﻿using Application.Job;
using FluentValidation;
using Infrastructure.DDD;
using Infrastructure.Extension;
using Infrastructure.WebApi;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Neoit.Utils;
using Quartz;
using Quartz.Impl.Matchers;

namespace Application.Service
{
    public class JobService : IJobService
    {
        private JobManagerDbContext _context { get; set; }
        private readonly Accessor _accessor;
        private readonly ISchedulerFactory _schedulerFactory;
        public JobService(JobManagerDbContext DbContext, Accessor accessor, ISchedulerFactory schedulerFactory)
        {
            _context = DbContext;
            _accessor = accessor;
            _schedulerFactory = schedulerFactory;
        }

        #region 获取列表
        public async Task<Page<JobResponse>> Get(JobRequest request)
        {
            var data = await _context.JobTask.AsQueryable()
                .WhereIf(request.Keyword.Has(), s => s.Title.Contains(request.Keyword) || s.Content.Contains(request.Keyword))
                .WhereIf(request.Group.Has(), s => s.Group.Contains(request.Group))
                .WhereIf(_accessor.RoleKey == GlobalData.Role.UserRoleKey, s => s.CreatorId == _accessor.ManagerId) //若是普通用户，则过滤数据
                .OrderByDescending(s=>s.IsEnable).ThenByDescending(s => s.CreateTime)
                .PageAsync(request.PageIndex, request.PageSize);

            TypeAdapterConfig<JobTask, JobResponse>.NewConfig()
                .Map(dest => dest.LastFailureTime, src => src.LastFailureTime == null ? "从未" : Convert.ToDateTime(src.LastFailureTime).TimeElapsed(TimeScale.year))
                .Map(dest => dest.LastSuccessTime, src => src.LastSuccessTime == null ? "从未" : Convert.ToDateTime(src.LastSuccessTime).TimeElapsed(TimeScale.year))
                .Map(dest => dest.JobType, src => src.JobType.GetName())
                .Map(dest => dest.Creator, src => src.CreatorId)
                .Map(dest => dest.LastExecutionTimeDes, src => src.LastExecutionTime == null ? "从未" : Convert.ToDateTime(src.LastExecutionTime).TimeElapsed(TimeScale.year))
                .Map(dest => dest.NextExecutionTimeDes, src => src.NextExecutionTime == null ? "无" : Convert.ToDateTime(src.NextExecutionTime).TimeHowLong(TimeScale.year, "过期", DateTime.Now));
            ;

            var list = data.List.Select(s => s.Adapt<JobResponse>()).ToList();

            #region Creator
            var ids = data.List.Select(s => s.CreatorId).Distinct().ToList();
            var users = await _context.SysManager.Where(s => ids.Contains(s.Id)).ToListAsync();
            list.ForEach(s => s.Creator = users.FirstOrDefault(a => a.Id.ToString() == s.Creator)?.UserName);
            #endregion

            #region IsScheduled
            if (await IsSingleService())
            {
                var _scheduler = await _schedulerFactory.GetScheduler();
                var listJob = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(JobGroupName.Task));
                list.ForEach(s => s.IsScheduled = listJob.Any(a => a.Name == s.Id.ToString()));
            }
            #endregion

            return new Page<JobResponse>
            {
                TotalCount = data.TotalCount,
                List = list
            };
        }
        public async Task<Page<JobTaskRecord>> GetRecords(JobRecordsRequest request)
        {
            await CheckRole(request.JobId);
            var data = await _context.JobTaskRecord.AsQueryable()
                .Where(s => s.JobId == request.JobId)
                .WhereIf(request.onlyFailed, s => !s.IsSuccess)
                .OrderByDescending(s => s.CreateTime)
                .PageAsync(request.PageIndex, request.PageSize);

            return data;
        }
        public async Task<List<NoticeDetails>> NoticeDetails(IdRequest request)
        {
            var oneRecord = await _context.JobTaskRecord.FirstOrDefaultAsync(s => s.Id == request.Id);
            await CheckRole(oneRecord.JobId);
            var data = await _context.Notice.Where(s => s.SourceId == request.Id).Select(s => new NoticeDetails
            {
                NoticeObject = s.NoticeObject,
                NoticeStatus = EnumHelper.GetDisplayName(s.NoticeStatus),
                SentResult = s.SentResult,
                SentTime = s.SentTime
            }).ToListAsync();
            return data;
        }
        public async Task<List<string>> GetGroups()
        {
            var datas = await _context.JobTask.GroupBy(s => s.Group).Select(s => s.Key).ToListAsync();
            return datas.Where(s => s.Has()).ToList();
        }
        #endregion

        #region CUD
        public async Task Add(JobFormRequest request)
        {
            CheckFrom(request);
            var model = new JobTask
            {
                IsEnable = true,
                CreatorId = _accessor.ManagerId,
                Title = request.Title,
                Cron = request.Cron,
                Content = request.Content,
                Expected = request.Expected,
                JobType = request.JobType,
                NoticeObjects = request.NoticeObjects,
                Group = request.Group,
                Note = request.Note,
                FailureRetryPolicy = ReSetFailureRetryPolicy(request.FailureRetryPolicy),
                RandomDelay = Convert.ToInt32(request.RandomDelay),

                RequestMethond = request.RequestMethond,
                RequestHeader = request.RequestHeader,
                RequestBody = request.RequestBody,
                LogRetentionDays = request.LogRetentionDays,

                NoticePolicy = request.NoticePolicy,
                NextJobId = request.NextJobId
            };
            await _context.JobTask.AddAsync(model);
            await _context.SaveChangesAsync();
            if (model.Cron.Has()) { await ScheduleJob(model, OperationType.Add); }
        }

        private string ReSetFailureRetryPolicy(string failureRetryPolicy)
        {
            return (failureRetryPolicy ?? "").Replace("，", ",").Split(",").Where(s => s.Has()).Select(s => s.Trim()).JoinString(",");
        }
        public async Task Copy(IdRequest request)
        {
            await CheckRole(request.Id);
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            var model = new JobTask
            {
                IsEnable = false,
                CreatorId = _accessor.ManagerId,
                Title = one.Title + "_副本",
                Cron = one.Cron,
                Content = one.Content,
                Expected = one.Expected,
                NoticeObjects = one.NoticeObjects,
                Group = one.Group,
                Note = one.Note,
                JobType = one.JobType,
                FailureRetryPolicy = one.FailureRetryPolicy,
                RandomDelay = one.RandomDelay,

                RequestMethond = one.RequestMethond,
                RequestHeader = one.RequestHeader,
                RequestBody = one.RequestBody,
                LogRetentionDays = one.LogRetentionDays,

                NoticePolicy = one.NoticePolicy,
                NextJobId = one.NextJobId
            };
            await _context.JobTask.AddAsync(model);
            await _context.SaveChangesAsync();
        }
        public async Task Update(JobFormRequest request)
        {
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            one.CheckNull();
            CheckFrom(request);
            await CheckRole(request.Id);
            one.Title = request.Title;
            one.Content = request.Content;
            one.Note = request.Note;
            one.Expected = request.Expected;
            one.NoticeObjects = request.NoticeObjects;
            one.Group = request.Group;
            one.JobType = request.JobType;
            one.FailureRetryPolicy = ReSetFailureRetryPolicy(request.FailureRetryPolicy);
            one.RandomDelay = Convert.ToInt32(request.RandomDelay);

            one.RequestMethond = request.RequestMethond;
            one.RequestHeader = request.RequestHeader;
            one.RequestBody = request.RequestBody;
            one.LogRetentionDays = request.LogRetentionDays;

            one.NoticePolicy = request.NoticePolicy;
            one.NextJobId = request.NextJobId;

            if (one.Cron != request.Cron)//启用状态下的更新cron才会加入调度队列
            {
                one.Cron = request.Cron;
                if (one.IsEnable&&one.Cron.Has())
                {
                    await ScheduleJob(one, OperationType.Update);
                    one.NextExecutionTime = SchedulerHelper.NextExecutionTime(one.Cron);
                }
            }
            await _context.SaveChangesAsync();
        }
        private void CheckFrom(JobFormRequest request)
        {
            request.StringTrim();

            var validator = new JobFormRequestValidator();
            var results = validator.Validate(request);// Execute the validator
            if (!results.IsValid) BusinessException.Throw(results.ToString());
        }

        private async Task CheckRole(int jobId)
        {
            if (_accessor.RoleKey == GlobalData.Role.UserRoleKey)
            {
                var exist = await _context.JobTask.AnyAsync(s => s.Id == jobId && s.CreatorId == _accessor.ManagerId);
                if (!exist) BusinessException.Throw(GlobalData.Tip.NoPermission);
            }
        }
        public async Task Delete(IdRequest request)
        {
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            one.CheckNull();
            await CheckRole(request.Id);
            one.Delete();
            if (one.IsEnable)
            {
                await ScheduleJob(one, OperationType.Delete);
            }
            await _context.SaveChangesAsync();
        }
        #endregion

        public async Task Once(RunOnceRequest request)
        {
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            one.CheckNull();
            await CheckRole(request.Id);
            var _scheduler = await _schedulerFactory.GetScheduler();

            var jobKey = new JobKey(one.Id.ToString(), JobGroupName.Task);
            bool jobExists = await _scheduler.CheckExists(jobKey);
            JobDataMap dataMap = new JobDataMap
            {
                { "isNowRunOnce", true },
                { "preTaskRecordId", request.PreTaskRecordId },
                { "dataId", request.Id }
            };
            if (jobExists)
            {
                await _scheduler.TriggerJob(jobKey, dataMap);
            }
            else
            {
                var job = JobBuilder.Create<TaskJob>().Build();
                ITrigger trigger = TriggerBuilder.Create().UsingJobData(dataMap).StartNow().Build();
                await _scheduler.ScheduleJob(job, trigger);
                _scheduler.ListenerManager.AddJobListener(new DeleteJobListener(), KeyMatcher<JobKey>.KeyEquals(job.Key));
            }
        }
        public async Task Enable(IdRequest request)
        {
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            await CheckRole(request.Id);
            if (one.IsEnable) return;
            one.IsEnable = true;
            one.ServerId = 0;
            if (one.Cron.Has())
            {
                one.NextExecutionTime = SchedulerHelper.NextExecutionTime(one.Cron);
                await ScheduleJob(one, OperationType.Enable);
            }
            await _context.SaveChangesAsync();
            
        }
        public async Task Disable(IdRequest request)
        {
            var one = await _context.JobTask.FirstOrDefaultAsync(s => s.Id == request.Id);
            await CheckRole(request.Id);
            if (one.IsEnable == false) return;
            one.IsEnable = false;
            one.ServerId = 0;
            await _context.SaveChangesAsync();
            await ScheduleJob(one, OperationType.Disable);
        }

        private async Task ScheduleJob(JobTask data, OperationType operationType)
        {
            var _scheduler = await _schedulerFactory.GetScheduler();
            var jobKey = new JobKey(data.Id.ToString(), JobGroupName.Task);
            var cron = data.Cron;
            if (await IsSingleService())
            {
                switch (operationType)
                {
                    case OperationType.Add:
                    case OperationType.Enable:
                        await _scheduler.RebuildJob(jobKey, cron);
                        data.ServerId = GlobalData.ServerId;
                        break;
                    case OperationType.Update:
                        if (await _scheduler.CheckExists(jobKey))
                        {
                            await _scheduler.RebuildJob(jobKey, cron);
                        }
                        break;
                    case OperationType.Disable:
                    case OperationType.Delete:
                        await _scheduler.DeleteJob(jobKey);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                var operationQueue = new OperationQueue
                {
                    JobId = data.Id,
                    ServerId = data.ServerId,
                    Type = operationType
                };
                switch (operationType)
                {
                    case OperationType.Add://待AssignJob调度
                    case OperationType.Enable://待AssignJob调度
                        break;
                    case OperationType.Update:
                        data.Cron = data.Cron;
                        await _context.OperationQueue.AddAsync(operationQueue);
                        break;
                    case OperationType.Disable:
                    case OperationType.Delete:
                        await _context.OperationQueue.AddAsync(operationQueue);
                        break;
                }
            }
            await _context.SaveChangesAsync();
        }

        /// <summary>
        /// 是否为单实例服务
        /// </summary>
        /// <returns></returns>
        private async Task<bool> IsSingleService()
        {
            var serverIds = await _context.KeyValue.Where(s => s.Type == KeyValueType.Server).Select(s => s.Id).ToListAsync();
            return serverIds.Count == 1 && serverIds[0] == GlobalData.ServerId;
        }
    }

    public interface IJobService
    {
        Task<Page<JobResponse>> Get(JobRequest request);
        Task<Page<JobTaskRecord>> GetRecords(JobRecordsRequest request);
        Task<List<NoticeDetails>> NoticeDetails(IdRequest request);
        Task<List<string>> GetGroups();

        Task Add(JobFormRequest request);
        Task Copy(IdRequest request);
        Task Update(JobFormRequest request);
        Task Delete(IdRequest request);
        Task Once(RunOnceRequest request);
        Task Enable(IdRequest request);
        Task Disable(IdRequest request);
    }
}
